#! /usr/bin/env bash

#  Licensed to the Apache Software Foundation (ASF) under one
#  or more contributor license agreements.  See the NOTICE file
#  distributed with this work for additional information
#  regarding copyright ownership.  The ASF licenses this file
#  to you under the Apache License, Version 2.0 (the
#  "License"); you may not use this file except in compliance
#  with the License.  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

NCPU=${NCPU:-$(( $(getconf _NPROCESSORS_ONLN) * 2 ))} # Numer of CPUs to build with
PORT=${PORT:-9090}                                    # Initial listen port for Traffic Server
VALGRIND=${VALGRIND:-N}                               # Whether to run under valgrind
TMPDIR=${TMPDIR:-/tmp}                                # Scratch directory for test instance construction

TSQA_FAIL=0                                         # Test failure count
TSQA_TESNAME=${TSQA_TESTNAME:-tsqa}                 # Name of current test
TSQA_ROOT=${TSQA_ROOT:-/tmp/$TSQA_TESTNAME.$RANDOM} # Filesystem root for current test
TSQA_TSXS=${TSQA_TSXS:-tsxs}

# Print a log message/
msg() {
  echo "MSG:" "$@"
}

msgwait() {
  local secs="$1"
  shift

  echo "MSG: waiting ${secs}s" "$@"
  sleep $secs
}

# Print a failure message and increment the failure count.
fail() {
  TSQA_FAIL=$(($TSQA_FAIL + 1))
  echo "FAIL:" "$@" 1>&2
}

# Fail and exit.
fatal() {
  TSQA_FAIL=$(($TSQA_FAIL + 1))
  echo "FATAL:" "$@" 1>&2
  exit $TSQA_FAIL
}

# Run a command and silence any output on stderr.
quiet() {
  "$@" 2>/dev/null
}

# Run a command with all output redirected to the pest log file.
logexec() {
  echo "$@" >> "$TSQA_ROOT/$TSQA_TESTNAME.log" 2>&1
  "$@" >> "$TSQA_ROOT/$TSQA_TESTNAME.log" 2>&1
}

tsxs() {
  $TSQA_TSXS "$@"
}

tsexec() {
  local cmd="$1"
  local run
  shift

  case $VALGRIND in
    y|yes|Y|YES|1) run="valgrind --trace-children=yes --trace-children-skip=env env" ;;
    *) run=env ;;
  esac

  # XXX enabling MallocStackLogging on all processes is annoying
  # because it logs 3 lines to stderr every time. We generally only
  # want leaks detection on traffic_server, so this is a bit of a
  # waste ...

  # MALLOC_CHECK_=2 => enable glibc malloc checking, abort on error
  # MallocStackLogging=1 => record OS X malloc stacks for leak checking
  $run \
  MALLOC_CHECK_=2 \
  MallocErrorAbort=1 \
  TS_ROOT=$TSQA_ROOT \
    $(bindir)/$cmd "$@"
}

reconfigure() {
  local srcdir="$1"
  msg running autoreconf in $srcdir ...
  (
    cd "$srcdir"
    autoreconf -i
  ) > autoreconf.log 2>&1
}

install () {
  [[ -d $BUILD ]] && rm -rf $BUILD
  [[ -d $PREFIX ]] && rm -rf $PREFIX

  msg installing ...
  mkdir -p $BUILD && (
    cd $BUILD
    $SRC/configure \
      --prefix=$PREFIX \
      --with-user=$(id -un) \
      --enable-debug \
      CCFLAGS=-O0 CXXFLAGS=-O0
    make -j $NCPU && make install
  ) > /dev/null

  msg installed to $PREFIX
}

logdir() {
  local prefix=$(tsxs -q PREFIX)
  tsxs -q LOGDIR | sed -es+$prefix/++
}

runtimedir() {
  local prefix=$(tsxs -q PREFIX)
  tsxs -q RUNTIMEDIR | sed -es+$prefix/++
}

sysconfdir() {
  local prefix=$(tsxs -q PREFIX)
  tsxs -q SYSCONFDIR | sed -es+$prefix/++
}

bindir() {
  tsxs -q BINDIR
}

# pidof(name): echo the pid of the given process name
pidof() {
  case "$1" in
  cop|manager|server);;
  *) fatal no such process name: $1
  esac
  quiet cat $TSQA_ROOT/$(runtimedir)/${1}.lock
}

# alive(name): Test whether the process "name" is alive.
alive() {
  local pid=$(pidof $1)
  if [[ ! -z "$pid" ]] ; then
    quiet kill -0 $pid
    return $?
  fi

  false
}

# Start up Traffic Server. Test for all the processes so that we have a better
# chance of delaying the test until traffic_server is ready.
startup() {
  local log=$TSQA_ROOT/$(logdir)/cop.log
  ( tsexec traffic_cop --stdout > $log )&
  for proc in cop manager server; do
    for i in $(seq 10) ; do
      alive $proc && msg $proc is alive && break
      sleep 1
    done
  done

  # And a final sleep to let traffic_server come up ...
  sleep 2
}

# Shut down Traffic Server.
shutdown() {

  # Quick'n'dirty cleanup of background jobs.
  jobs -p | while read pid ; do
    kill $pid
  done

  local pid=$(pidof cop)
  if [[ -z "$pid" ]] ; then
    return
  fi

# XXX If we are on Darwin, we can check the traffic_server for leaks before shutting down, but
# we really only want to do this for traffic_server ... or we should sink the output to the test
# log and fail on the exit status.

#  if [ -x /usr/bin/leaks ]; then
#    msg checking for leaks ...
#    /usr/bin/leaks $(pidof server)
#  fi

  msg shutting down ...
  while quiet kill -0 $pid ; do
    quiet kill -TERM $pid
    pid=$(pidof cop)
    if [[ -z "$pid" ]] ; then
      return
    fi
  done

  exit $TSQA_FAIL
}

restart() {
  local pid=$(pidof cop)
  if [[ -z "$pid" ]] ; then
    return
  fi

  msg shutting down ...
  while quiet kill -0 $pid ; do
    quiet kill -TERM $pid
    pid=$(pidof cop)
    if [[ -z "$pid" ]] ; then
      return
    fi
  done

  startup
}

# Test for Traffic Server crash logs.
crash() {
  local outfile="$TSQA_ROOT/$(logdir)/traffic.out"

  msg checking for crashes ...
  for i in $(seq 10); do
    sleep 1
    [[ -e $outfile ]] && \
      grep -a -A 10 "STACK TRACE" $outfile && \
      fail detected a crash
  done
}

# Bootstrap a TSQA test root. The result of this is an independent test root
# that contains all the variable parts of a traffic server configuration, while
# referring to the parent installation for the actual test binaries.
bootstrap() {
  local prefix=$(tsxs -q PREFIX)
  local sysconfdir=$(tsxs -q SYSCONFDIR | sed -es+$prefix/++)
  local dir
  local userid=${SUDO_USER:-$(whoami)}

  # Create runtime directories in the test root.
  for dir in SYSCONFDIR LOCALSTATEDIR RUNTIMEDIR LOGDIR ; do
    local p=$(tsxs -q $dir | sed -es+$prefix/++)
    mkdir -p $TSQA_ROOT/$p
  done

  # Copy config across
  cp -r $(tsxs -q SYSCONFDIR)/*.config $TSQA_ROOT/$sysconfdir

  # Delete any config variables we are about to set.
  sed -i.orig \
    -e/proxy.config.body_factory.template_sets_dir/d \
    -e/proxy.config.plugin.plugin_dir/d \
    -e/proxy.config.bin_path/d \
    -e/proxy.config.admin.user_id/d \
    -e/proxy.config.diags/d \
    -e/proxy.config.http.server_ports/d \
    -e/proxy.config.config_update_interval_ms/d \
    $TSQA_ROOT/$sysconfdir/records.config || fatal failed to initialize records.config

  cat >> $TSQA_ROOT/$sysconfdir/records.config <<EOF
CONFIG proxy.config.bin_path STRING $(tsxs -q BINDIR)
CONFIG proxy.config.plugin.plugin_dir STRING $(tsxs -q LIBEXECDIR)
CONFIG proxy.config.body_factory.template_sets_dir STRING $(tsxs -q SYSCONFDIR)/body_factory

CONFIG proxy.config.admin.user_id STRING $userid
CONFIG proxy.config.http.server_ports STRING $PORT

# Flush config updates every 0.5s so that we don't have to sleep so long making config changes.
CONFIG proxy.config.config_update_interval_ms INT 500

# Send all diagnostics to both traffic.out and diags.log.
CONFIG proxy.config.diags.output.diag STRING OL
CONFIG proxy.config.diags.output.debug STRING OL
CONFIG proxy.config.diags.output.status STRING OL
CONFIG proxy.config.diags.output.note STRING OL
CONFIG proxy.config.diags.output.warning STRING OL
CONFIG proxy.config.diags.output.error STRING OL
CONFIG proxy.config.diags.output.fatal STRING OL
CONFIG proxy.config.diags.output.alert STRING OL
CONFIG proxy.config.diags.output.emergency STRING OL

CONFIG proxy.config.diags.debug.enabled INT 1
CONFIG proxy.config.diags.debug.tags STRING NULL
CONFIG proxy.config.diags.show_location INT 1
EOF

  chown -R "$userid" $TSQA_ROOT

  msg bootstrapped Traffic Server into $TSQA_ROOT
  tsexec traffic_layout
}

# vim: set sw=2 ts=2 et :
